import re
from django import forms
from django.db.models import Q # Q查询
from django.contrib.auth import login
from django_redis import get_redis_connection
from verifications.constants import SMS_CODE_NUMS
from . import constants
from .models import Users


# 校验数据 1.用户名 不能为空 格式是否正确 是否注册过
# 2.密码和确认密码中的内容一致
# 3.手机号 不能为空 格式是否正确 是否注册过
# 4.手机短信验证码 不为空 与数据库中存入的数据一样

class RegisterForm(forms.Form):
    """注册的form表单验证"""
    # form表单的单个字段验证
    username = forms.CharField(label='用户名', max_length=20,
                               min_length=5,
                               error_messages={
                                   "min_length": "用户名长度要大于5",
                                   "max_length": "用户名长度要小于20",
                                   "required": "用户名不能为空"})
    password = forms.CharField(label='密码', max_length=20,
                               min_length=6,
                               error_messages={
                                   "min_length": "密码长度要大于6",
                                   "max_length": "密码长度要小于20",
                                   "required": "密码不能为空"})
    password_repeat = forms.CharField(label='确认密码',
                                      max_length=20,
                                      min_length=6,
                                      error_messages={
                                          "min_length": "密码长度要大于6",
                                          "max_length": "密码长度要小于20",
                                          "required": "密码不能为空"})
    mobile = forms.CharField(label='手机号', max_length=11, min_length=11,
                             error_messages={
                                 "min_length": "手机号长度有误",
                                 "max_length": "手机号长度有误",
                                 "required": "手机号不能为空"})
    sms_code = forms.CharField(label='短信验证码',
                               max_length=SMS_CODE_NUMS,
                               min_length=SMS_CODE_NUMS,
                               error_messages={
                                   "min_length": "短信验证码长度有误",
                                   "max_length": "短信验证码长度有误",
                                   "required": "短信验证码不能为空"})

    # 在发送短信验证码是对手机号字段用的是校验器校验 这回用另一种方法校验 使用方法校验
    # 单个字段可以进行校验   单个字段使用[ clean_mobile(self) ] 自己定义一个方法
    # 多个字段也可以一起校验  多个字段使用[ clean(self) ]方法 需要重写父类的clean方法
    def clean_mobile(self):
        """单个字段mobile进行验证"""
        # 对单个字段进行校验不需要继承父类  因为这个是我们自己写的方法 父类中没有这个方法
        tel = self.cleaned_data.get('mobile')
        if not re.match(r"^1[3-9]\d{9}$", tel):# 使用re模块中的match方法进行校验(正则)
            raise forms.ValidationError("手机号码格式不正确")
        if Users.objects.filter(mobile=tel).exists():# 查询数据库中手否存在这个手机号
            # QuerySet中exists方法判断是否存在 返回值是True或False
            raise forms.ValidationError("手机号已注册，请重新输入！")
        # 需要return返回一个返回值 会替换掉cleaned_data中原来字段对应的值  为了进行多个字段的联合校验
        return tel

    def clean(self):
        """多个字段联合校验 继承父类 在父类的功能中在补上父类没有的东西"""
        # 多个字段也可以一起校验 继承父类的clean方法 字典类型数据
        cleaned_data = super().clean()
        username = cleaned_data.get('username')
        passwd = cleaned_data.get('password')
        passwd_repeat = cleaned_data.get('password_repeat')

        if Users.objects.filter(username=username).exists():
            # 判断手机号是否注册
            raise forms.ValidationError("用户名存在，请重新输入")
        if passwd != passwd_repeat:
            # 判断密码的确认密码是否相等
            raise forms.ValidationError("两次密码不一致")

        # 获取是手机号和短信验证码
        tel = cleaned_data.get('mobile')
        sms_text = cleaned_data.get('sms_code')

        # 建立redis连接
        redis_conn = get_redis_connection(alias='verify_codes')
        # 创建 存入redis数据库中的短信验证码的键
        sms_fmt = "sms_{}".format(tel)
        # 根据键获取到redis中对应的值 记得解码
        real_sms = redis_conn.get(sms_fmt).decode('utf-8')

        # 判断短信验证码是否存在 或者 用户输入的短信验证码和数据库中的相同
        if (not real_sms) or (sms_text != real_sms):
            raise forms.ValidationError("短信验证码错误")

    # 1.单个字段格式的验证
    # 2.手机号,格式,是否已注册
    # 3.密码是否一致
    # 4.短信验证码是否跟数据库一致




# 校验 1.用户帐户 输入的是手机号还是用户名 格式是否正确 是否为空
# 2. 账户名和密码和数据库中的数据一致

class LoginForm(forms.Form):
    """登录的form表单验证 user_account：用户帐号，password：密码，remember_me：记住我"""
    # 单个字段的验证
    user_account = forms.CharField() # 用户输入的可能是用户名也可能是手机号
    # 手机号和用户名的验证规则不同 无法准确验证用 用户帐户 这单个字段
    # 和模型类不同表单的CharField()可以空着不写
    password = forms.CharField(label='密码', max_length=20, min_length=6,
                               error_messages={"min_length": "密码长度要大于6",
                                               "max_length": "密码长度要小于20",
                                               "required": "密码不能为空"})
    # 若要表示一个字段不是必需的 设置required=False
    remember_me = forms.BooleanField(required=False)

    def __init__(self, *args, **kwargs):
        """初始化参数 """
        # 获取值 重写__init__方法添加我们需要的字段
        # 我们多传入了一个父类没有的参数request
        # 所以新添加的参数需要写在前面 然后再继承父类的参数
        print(kwargs)
        self.request = kwargs.pop('request', None) # 添加新的参数
        print(self.request)
        super(LoginForm, self).__init__(*args, **kwargs) # 继承父类的初始化中的参数


    # 对用户帐号字段进行验证 定义 clean_字段名 方法来验证字段
    def clean_user_account(self):
        """对账户名字段的验证自定义的方法"""
        user_info = self.cleaned_data.get('user_account')
        # 判断用户帐户是否为空
        if not user_info:
            raise forms.ValidationError("用户账号不能为空")
        # 判断用户帐户是否格式正确
        if not re.match(r"^1[3-9]\d{9}$", user_info) \
                and (len(user_info) < 5 or len(user_info) > 20):
            raise forms.ValidationError("用户账号格式不正确，请重新输入")
        # 需要return返回一个返回值 会替换掉cleaned_data中原来字段对应的值  为了进行多个字段的联合校验
        return user_info

    def clean(self):
        """多个字段联合校验 继承父类 在父类的功能中在补上父类没有的东西"""
        cleaned_data = super().clean()
        # 1.数据库查找数据   获取单个字段验证方法验证之后的用户账号
        user_info = cleaned_data.get('user_account')
        passwd = cleaned_data.get('password') # 获取明文的密码
        hold_login = cleaned_data.get('remember_me')

        # 在form表单中实现登录逻辑
        # 使用Q查询 联合查找数据库中的数据  | 或者
        user_queryset = Users.objects.filter(Q(mobile=user_info) |
                                             Q(username=user_info))
        if user_queryset: # 判断数据库中是否存在用户账号
            user = user_queryset.first() # 存在用户账号取第一个对象
            # 使用check_passwor方法把用户输入的密码和数据库中的加密的密码进行比对
            if user.check_password(passwd): # 判断用户输入的密码是否正确
                # 2.remember_me(记住我)的勾选 和 用户的登录操作
                # 记住我的勾选就是设置session信息存在的时间是多久 没勾上是多久(可以自行设置)
                # 勾上了是多久(可以自行设置) session信息存在时间的设置在框架班第十一节
                if hold_login:  # 判断是否勾选了记住我 redis中保存session信息的时间
                    self.request.session.set_expiry(None)
                    # session信息过期时间set_expiry()方法None是默认时间2周
                else:
                    # session信息过期时间set_expiry()方法0是会话结束就失效
                    self.request.session.set_expiry(0)
                # 使用login来登录用户  login(request对象,用户实例)
                login(self.request, user)
                # login(request,user)相当于request.session['user']=user .[键]=值
                # auth中的login函数帮我们完成了 session状态保持设置 只需要传入相应的参数
            else:# 用户输入的密码错误时抛出的错误
                raise forms.ValidationError("密码不正确，请重新输入")

        else:# 用户账号不存在时抛出的错误
            raise forms.ValidationError("用户账号不存在，请重新输入")
